package com.iflytec.download;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicLong;

@Component
public class DownloadTestUtil {

    private static final Logger logger = LoggerFactory.getLogger(DownloadTestUtil.class);

    private static final int BUFFER_SIZE = 8192; // 每次读取的缓冲区大小（8 KB）

    private static final int MAX_RETRIES = 5;

    private static final int INITIAL_BACKOFF_MS = 2000; // 初始退避时间：2秒

    private static final int MAX_BACKOFF_MS = 30000; // 最大退避时间：30秒

    private static final int THREAD_COUNT = Runtime.getRuntime().availableProcessors() * 2; // 设置下载线程数

    private static final int CHUNK_SIZE = 10 * 1024 * 1024;  // 每个分片 10MB

    private static final int CONNECT_TIMEOUT = 20000; // 10 seconds for connection timeout

    private static final int READ_TIMEOUT = 20000;// 10 seconds for read timeout


    public static void main(String[] args) throws Exception {
        String url = "https://vdept3.bdstatic.com/mda-qmfinpvwmvymhs8e/360p/h264/1734356030067613385/mda-qmfinpvwmvymhs8e.mp4?v_from_s=hkapp-haokan-suzhou&auth_key=1737958250-0-0-068bb8cf14b2dfe9af53416f776593ff&bcevod_channel=searchbox_feed&cr=0&cd=0&pd=1&pt=3&logid=0650079212&vid=16232853081927957277&klogid=0650079212&abtest=";
//        String url = "https://vdept3.bdstatic.com/mda-qj54c65u5tscw2ib/cae_h264/1728209667410119203/mda-qj54c65u5tscw2ib.mp4?v_from_s=hkapp-haokan-suzhou&auth_key=1737908090-0-0-5a314dc5b4a0603dad59c384de871e89&bcevod_channel=searchbox_feed&cr=0&cd=0&pd=1&pt=3&logid=0890584547&vid=6367308007963086157&klogid=0890584547&abtest=";
        String targetPath = "C:\\Users\\84186\\Desktop\\temp\\";
        downloadFile(url, targetPath);
    }


    /**
     * 文件下载
     *
     * @return
     * @throws Exception
     */
    public static void downloadFile(String fileURL, String targetPath) throws Exception {
        try {
            long startTime = System.currentTimeMillis();
            downloadFileMultiThread(fileURL, targetPath);
            long endTime = System.currentTimeMillis();
            logger.info("网络路径文件下载结束-->总耗时: {}分钟", (endTime - startTime) / 1000 / 60);

        } catch (Exception e) {
            logger.error("下载出错:{}", e.getMessage());
            throw e;
        }
    }

    public static void downloadFileMultiThread(String fileURL, String targetPath) throws Exception {
        // 获取文件总大小
        HttpURLConnection connection = (HttpURLConnection) new URL(fileURL).openConnection();
        connection.setRequestMethod("HEAD"); // 只请求文件头信息
        int fileSize = connection.getContentLength();

        connection.disconnect();

        logger.info("网络路径文件下载开始-->当前文件ID: {},下载文件大小: {}M", 1, Math.ceil((double) fileSize / 1024 / 1024));

        // 计算每个线程下载的部分大小
        int chunksCount = (fileSize + CHUNK_SIZE - 1) / CHUNK_SIZE;

        // 用于记录总体下载进度
        AtomicLong totalBytesDownloaded = new AtomicLong(0);

        // 使用线程池进行并发下载
        ThreadPoolExecutor executorService = ThreadPoolUtil.newThreadPool(THREAD_COUNT, "chunk-thread-");
        List<Future<Void>> futures = new ArrayList<>();

        // 启动线程下载文件的各个部分
        for (int i = 0; i < chunksCount; i++) {
            long startByte = i * CHUNK_SIZE;
            long endByte = Math.min(startByte + CHUNK_SIZE - 1, fileSize - 1);
            futures.add(executorService.submit(
                    new DownloadTask(fileURL, targetPath, startByte, endByte, i + 1, totalBytesDownloaded)));
        }

        // 定期打印下载进度
        ThreadPoolExecutor progressExecutor = ThreadPoolUtil.newThreadPool(1, "progress-thread-");
        progressExecutor.submit(() -> {
            int lastReportedProgress = 0;
            while (true) {
                long downloadedBytes = totalBytesDownloaded.get();
                int progress = (int) ((downloadedBytes * 100) / fileSize);
                // 每当下载进度增加了 10% 时才记录进度
                if (progress >= lastReportedProgress + 10) {
                    lastReportedProgress = progress;
                    logger.info("网络路径总体下载进度: {}% ({}/{} bytes)", progress, downloadedBytes, fileSize);
                }
                // 当下载完成时退出
                if (downloadedBytes == fileSize) {
                    break;
                }
            }
        });

        // 等待所有任务完成
        waitForCompletion(futures);


        executorService.shutdown();
        progressExecutor.shutdown();
        logger.info("网络路径分片下载线程池已关闭!");

        // 等待文件下载完成后再进行合并，确保文件顺序正确
        String resultPath = "C:\\Users\\84186\\Desktop\\downloadFile.mp4";
        mergeFileChunks(targetPath, resultPath, chunksCount);
        verifyFileIntegrity(resultPath, fileSize);
    }

    private static boolean isValidVideoFile(String filePath) {
        // 使用 FFmpeg 检查文件头信息，只读取必要的元数据
        try {
            // 通过添加 -t 0 参数来告诉 FFmpeg 仅分析文件的头部，而不解码整个文件
            Process process = new ProcessBuilder("E:\\ffmpeg-2025-01-22-git-e20ee9f9ae-full_build\\bin\\ffmpeg", "-v", "error", "-i", filePath, "-t", "0", "-f", "null", "-").start();
            int exitCode = process.waitFor();
            return exitCode == 0; // 如果没有错误，说明是有效的视频文件
        } catch (IOException | InterruptedException e) {
            logger.error("文件格式验证失败: {}", e.getMessage());
            return false;
        }
    }

    private static void mergeFileChunks(String targetPath, String resultPath, int chunksCount) throws IOException {
        try (RandomAccessFile outputFile = new RandomAccessFile(resultPath, "rw")) {
            for (int i = 0; i < chunksCount; i++) {
                File chunkFile = new File(targetPath + "part" + (i + 1));
                if (!chunkFile.exists()) {
                    logger.error("分片文件 {} 不存在", chunkFile.getPath());
                    throw new IOException("分片文件不存在: " + chunkFile.getPath());
                }
                try (RandomAccessFile chunk = new RandomAccessFile(chunkFile, "r")) {
                    byte[] buffer = new byte[BUFFER_SIZE];
                    int bytesRead;
                    while ((bytesRead = chunk.read(buffer)) != -1) {
                        outputFile.write(buffer, 0, bytesRead);
                    }
                }
                chunkFile.delete(); // 删除分片文件
            }
        }
    }

    // 校验文件完整性（可以通过文件大小、校验和等方式进行）
    private static void verifyFileIntegrity(String localPath, long expectedSize) throws IOException {
        Path targetPath = Paths.get(localPath);
        // 验证文件大小是否符合预期
        File file = targetPath.toFile();
        if (file.length() != expectedSize) {
            logger.error("网络路径文件大小不匹配,预期文件大小: {},实际下载文件大小: {},大小相差: {}", expectedSize, file.length(), expectedSize - file.length());
            throw new IOException("网络路径下载的文件大小不匹配！");
        }
        logger.info("网络路径文件完整性校验通过，大小符合预期：{}M", Math.ceil((double) expectedSize / 1024 / 1024));
        if (!isValidVideoFile(localPath)) {
            logger.error("文件格式错误!");
            throw new IOException("文件格式错误!");
        }
        logger.info("文件格式校验成功!");
    }

    // 等待所有下载任务完成
    private static void waitForCompletion(List<Future<Void>> futures) {
        for (Future<Void> future : futures) {
            try {
                future.get();
            } catch (ExecutionException e) {
                logger.error("网络路径文件下载出错: {}", e.getMessage());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                logger.error("网络路径文件下载中断: {}", e.getMessage());
            }
        }
    }

    private static class DownloadTask implements Callable<Void> {
        private final String fileURL;
        private final String destinationFilePath;
        private final long startByte;
        private final long endByte;
        private final int partNumber;
        private final AtomicLong totalBytesDownloaded;

        public DownloadTask(String fileURL, String destinationFilePath, long startByte, long endByte, int partNumber, AtomicLong totalBytesDownloaded) {
            this.fileURL = fileURL;
            this.destinationFilePath = destinationFilePath;
            this.startByte = startByte;
            this.endByte = endByte;
            this.partNumber = partNumber;
            this.totalBytesDownloaded = totalBytesDownloaded;
        }

        @Override
        public Void call() throws Exception {
            int retryCount = 0;
            boolean success = false;

            while (retryCount < MAX_RETRIES && !success) {
                try {
                    success = attemptDownload();
                } catch (Exception e) {
                    retryCount++;
                    logger.warn("网络路径分片part {} 下载出错，重试第 {} 次: {}", partNumber, retryCount, e.getMessage());
                    // 指数退避策略：每次重试等待的时间越来越长
                    int backoffTime = Math.min(INITIAL_BACKOFF_MS * (1 << retryCount), MAX_BACKOFF_MS);
                    logger.warn("网络路径分片part {} 等待 {} 毫秒后重试...", partNumber, backoffTime);
                    if (retryCount < MAX_RETRIES) {
                        try {
                            Thread.sleep(backoffTime); // 等待一段时间后重试
                        } catch (InterruptedException ex) {
                            Thread.currentThread().interrupt();
                        }
                    }
                }
            }

            if (!success) {
                throw new IOException("网络路径分片part " + partNumber + " 下载失败，已重试 " + MAX_RETRIES + " 次");
            }

            return null;
        }

        private boolean attemptDownload() throws Exception {
            HttpURLConnection connection = (HttpURLConnection) new URL(fileURL).openConnection();
            connection.setRequestProperty("Range", "bytes=" + startByte + "-" + endByte);
            connection.setConnectTimeout(CONNECT_TIMEOUT); // 设置连接超时
            connection.setReadTimeout(READ_TIMEOUT); // 设置读取超时
            connection.setRequestProperty("Connection", "keep-alive");
            connection.connect();
            String chunkFileName = destinationFilePath + "part" + partNumber;
            try (InputStream in = connection.getInputStream(); RandomAccessFile file = new RandomAccessFile(chunkFileName, "rw")) {
                file.seek(0);
                byte[] buffer = new byte[BUFFER_SIZE];
                int bytesRead;
                while ((bytesRead = in.read(buffer)) != -1) {
                    file.write(buffer, 0, bytesRead);
                    totalBytesDownloaded.addAndGet(bytesRead);  // 更新已下载的字节数
                }
            } finally {
                connection.disconnect();
            }
            verifyChunkFile(chunkFileName, endByte - startByte + 1);
            logger.info("网络路径分片Part {} 下载完成!", partNumber);
            return true;
        }

        private void verifyChunkFile(String chunkPath, long expectedSize) throws IOException {
            Path targetPath = Paths.get(chunkPath);
            // 验证文件大小是否符合预期
            File file = targetPath.toFile();
            if (file.length() != expectedSize) {
                logger.error("分片文件part{}大小不匹配,预期文件大小: {},实际下载文件大小: {},大小相差: {}", partNumber, expectedSize, file.length(), expectedSize - file.length());
                throw new IOException("分片文件大小不匹配！");
            }
            logger.info("分片文件part{}完整性校验通过，大小符合预期：{}M", partNumber, Math.ceil((double) expectedSize / 1024 / 1024));
        }

    }
}